package nqueens.chessStuff;

import java.util.*;

public class ChessBoard {

    private int n; // the size of the problem set

    private final Square squares [][];
    private Set <Queen> queenSet;
    private final boolean VALIDATE_SOLUTIONS;

    /**
     * Constructor for the chessboard class
     * @param n the size of the problem set
     * @param validateSolutions a boolean flag to dictate whether or not to validate solutions
     */
    public ChessBoard (int n, boolean validateSolutions) {
        this.VALIDATE_SOLUTIONS = validateSolutions;
        this.n = n;
        queenSet = new  TreeSet<Queen>();
        // initialize the queens array
        for (int k = 0; k < n; k++) {
            queenSet.add(new Queen (k));
        }

        // initialize the squares for the chessboard
        squares = new Square [n][n];
        for (int r = 0; r < getN()  ; r++ ) {
            for (int c = 0; c < getN() ; c++ ) {
                squares[r][c] = new Square(r,c);
            }
        }
    }

    /**
     *  Constructor for the chessboard class
     * @param n the size of the problem set
     */
    public ChessBoard (int n) {
        this(n,true);
    }

    /**
     * Method to validate that the current queen placement is a solution.
     * NOTE: if queen placement is NOT being validated, then this will always return true.
     * @return a boolean for whether or not the current queen placement is a solution, or true if
     * validation has been disabled.
     */
    public boolean isSolved () {
        for (Queen q : queenSet) {
            // if any of the queens don't have a square then they are not placed
            // or if one of the squares a queen is place on is threatened
            if ( q.getSquare() == null || q.getSquare().isAttacked()) {
                return false;
            }
        }
        return true;
    }

    /**
     * Get N which represents the size of the problem set
     * @return the value of N which is the problem set
     */
    public int getN () {
        return n;
    }

    /**
     * Returns a specific chessboard square
     * @param row the row of the square to be returned
     * @param column the column of the square to be returned
     * @return the square requested at the referenced location
     */
    public Square getChessBoardSquare (int row, int column) {
        return squares[row][column];
    }

    /**
     * Returns a set of the queens that correlate to this specific chessboard
     * @return a set of Queens that need to be placed on this chessboard
     */
    public Set <Queen> getQueenSet() {
        return queenSet;
    }

    /**
     * This method will attempt to place a queen at the specified location on the board
     * @param row the row to place the queen on from 0 to n-1
     * @param col the column to place the queen on from 0 to n-1
     * @return a true or false flag representing whether or not the placement was successful
     * @throws InterruptedException this is to bubble up an interrupt thrown by the processing thread
     */
    public boolean placeQueen (int row, int col) throws InterruptedException {
        // If the square is already occupied, return a false value for the placement
        if (squares[row][col].isOccupied()) {
            return false;
        }
        else {
            for(Queen q : queenSet) {
                // look for an unplaced queen, and place it on the specified square
                if (q.getSquare() == null) {
                    squares[row][col].placeQueen(q);
                    q.placeQueen(getChessBoardSquare(row, col));
                    if (VALIDATE_SOLUTIONS) {
                        markAttacked(row, col);
                    }
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * Returns the queen placed on this specific square or null if no queen is present.
     * @param row The row the queen is placed on indexed from 0 to n-1
     * @param col The column the queen is placed on indexed from 0 to n-1
     * @return The queen at the specific location
     */
    public Queen getOneQueen (int row, int col) {
        for (Queen qn : queenSet) {
            if (qn.getSquare() != null && qn.getSquare().equals(new Square(row,col))) {
                return qn;
            }
        }
        return null;
    }

    /**
     * Removes a queen from a specific square based on the squares row and column location.
     * This method returns the queen who was removed from that square, or it returns null if
     * no queen is located there.
     * @param row The row location of the queen to be removed
     * @param col The column location of the queen to be removed
     * @return The queen who was removed or a null value
     */
    public Queen removeQueen(int row, int col) {
        if (!squares[row][col].isOccupied()) {
            return null;
        }
        else {
            for(Queen q : queenSet) {
                // look for the specified queen and remove it
                if (q.getSquare() != null && q.getSquare().equals(getChessBoardSquare(row, col))) {
                    squares[row][col].removeQueen();
                    q.removeQueen();
                    if (VALIDATE_SOLUTIONS) {
                        removeAttacked(row, col);
                    }
                    return q;
                }
            }
            return null;
        }
    }

    /**
     * Removes a queen from the board and returns the square the queen was on.  Returns null if the
     * queen was not on the board to begin with.
     * @param q The queen to be removed
     * @return The value of the Square the queen was taken from.  Null if not on the board.
     */
    public Square removeQueen (Queen q) {
        Square tempSquare = null;
        for (Queen qn : queenSet) {
            if (qn.equals(q)) {
                tempSquare = qn.getSquare();
                qn.removeQueen();
                squares[tempSquare.getRow()][tempSquare.getColumn()].removeQueen();
                if (VALIDATE_SOLUTIONS) {
                    removeAttacked(tempSquare.getRow(), tempSquare.getColumn());
                }
                return tempSquare;
                }
            }
        return tempSquare;

    }

    /**
     * This method prints out the queens in the queen set and specifies their locations.
     * This was used mostly for debugging and validation purposes and is disabled in the submitted version.
     */
    public void displayQueens() {
            System.out.printf("This board has %d queens placed at:\n", queenSet.size());

        // prints out the locations of all the queens
        for (Queen q : queenSet) {
            if (q.getSquare() != null) {
                System.out.printf("The queenSet Queen %d is located at row %d, column %d\n",q.IDENTIFIER,
                        q.getSquare().getRow(),q.getSquare().getColumn());
            }
            else {
                System.out.printf("QueenSet queen with identifier %d, is not on the board!\n",q.IDENTIFIER);
            }
        }
    }

    /**
     * This method prints out the present chessboard.  This was used mostly for debugging
     * and validation purposes and is disabled in the submitted version.
     */
    public void displayBoard () {

        displayQueens(); // displays the queens

        // prints out the chessboard
        for (int r = 0; r < n  ; r++ ) {
            System.out.println("");
            for (int c = 0; c < n ; c++ ) {
                if (squares[r][c].isOccupied()) {
                    System.out.printf("%-3s", "Q");
                }
                else {
                    System.out.printf("%-3s", "*");
                }
            }
        }
        System.out.println("");
        System.out.println("");
    }

    /**
     * This is a private helper method for when a queen is removed from the board.  When a
     * queen is removed, this method gets called on the square the queen was removed from and it
     * will unflag the row, column, and both diaginals that the queen was 'attacking'.  'Attacking'
     * implies the square was threatened by this queen.
     * @param row The row of the square to be cleared of 'attacks'
     * @param col The column of the square to be cleared of 'attacks'
     */
    private void removeAttacked(int row, int col) {
        for (Square sq[] : squares) {
            if (sq[col].getRow() != row) {
                sq[col].decrementAttacked();
            }
        }

        // mark the row except the square the queen is in
        for (Square sq : squares[row]) {
            if (sq.getColumn() != col) {
                sq.decrementAttacked();
            }
        }

        // mark down to the right
        for (int r = row + 1, c = col + 1; r < getN() && c < getN(); r++, c++) {
            squares[r][c].decrementAttacked();
        }
        // mark up to the left
        for (int r = row - 1, c = col - 1; r >= 0 && c >= 0; r--, c--) {
            squares[r][c].decrementAttacked();
        }
        // mark up to the right
        for (int r = row - 1, c = col + 1; r >= 0 && c < getN(); r--, c++) {
            squares[r][c].decrementAttacked();
        }
        // mark down to the left
        for (int r = row + 1, c = col - 1; r < getN() && c >= 0; r++, c--) {
            squares[r][c].decrementAttacked();
        }

    }

    /**
     * This is a private helper method for when a queen is placed on the board.  When a
     * queen is placed, this method gets called on the square the queen was placed on and it
     * will flag the row, column, and both diaginals that the queen is now 'attacking'.  'Attacking'
     * implies the squares are now threatened by this queen's placement.  As an aside, the overhead for
     * calling 'checkInterrupt()' so often is high, but it stops the backtracking method shortly
     * after the interrupt is sent.
     * @param row The row of the square to be flagged for 'attacked' by a queen
     * @param col The column of the square to be flagged as 'attacked' by a queen
     * @throws InterruptedException
     */
    private void markAttacked(int row, int col) throws InterruptedException {

        // mark the column except the square the queen is in
        for (Square sq[] : squares) {
            if (sq[col].getRow() != row) {
                sq[col].incrementAttacked();
            }
        }
        checkInterrupt();

        // mark the row except the square the queen is in
        for (Square sq : squares[row]) {
            if (sq.getColumn() != col) {
                sq.incrementAttacked();
            }
        }
        checkInterrupt();

        // mark down to the right
        for (int r = row + 1, c = col + 1; r < getN() && c < getN(); r++, c++) {
            squares[r][c].incrementAttacked();
        }
        checkInterrupt();
        // mark up to the left
        for (int r = row - 1, c = col - 1; r >= 0 && c >= 0; r--, c--) {
            squares[r][c].incrementAttacked();
        }
        checkInterrupt();
        // mark up to the right
        for (int r = row - 1, c = col + 1; r >= 0 && c < getN(); r--, c++) {
            squares[r][c].incrementAttacked();
        }
        checkInterrupt();
        // mark down to the left
        for (int r = row + 1, c = col - 1; r < getN() && c >= 0; r++, c--) {
            squares[r][c].incrementAttacked();
        }
        checkInterrupt();
    }

    /**
     * Another private helper method that bubbles up the interrupt for killing the thread.
     * This was added to check for interrupts more often.
     * @throws InterruptedException An interrupt was received by the parent thread
     */
    private void checkInterrupt() throws InterruptedException {
        if (Thread.currentThread().isInterrupted()) {
            throw new InterruptedException();
        }
    }
}
